package com.xbongbong.saas.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xbongbong.i18n.util.I18nMessageUtil;
import com.xbongbong.paas.constant.MessageConstant;
import com.xbongbong.paas.constant.PaasConstant;
import com.xbongbong.paas.constant.SizeConstant;
import com.xbongbong.paas.domain.entity.PaasFormEntity;
import com.xbongbong.paas.domain.entity.PaasFormExplainEntity;
import com.xbongbong.paas.domain.entity.ext.PaasFormDataEntityExt;
import com.xbongbong.paas.domain.entity.ext.PaasFormEntityExt;
import com.xbongbong.paas.elasticsearch.model.PaasEsModel;
import com.xbongbong.paas.elasticsearch.util.EsHelper;
import com.xbongbong.paas.elasticsearch.util.EsUtil;
import com.xbongbong.paas.elasticsearch.util.XbbElasticsearchRestTemplate;
import com.xbongbong.paas.enums.DelEnum;
import com.xbongbong.paas.enums.FieldTypeEnum;
import com.xbongbong.paas.enums.FlowStatusEnum;
import com.xbongbong.paas.enums.IndexTypeEnum;
import com.xbongbong.paas.field.FieldAttrEntity;
import com.xbongbong.paas.model.PaasFormExplainModel;
import com.xbongbong.paas.model.PaasFormModel;
import com.xbongbong.paas.pojo.FieldAttrEntityForImport;
import com.xbongbong.paas.pojo.FormTitlePoJo;
import com.xbongbong.paas.pojo.SummaryDataPoJo;
import com.xbongbong.paas.pojo.dto.FormDataAddDTO;
import com.xbongbong.paas.pojo.dto.FormDataListDTO;
import com.xbongbong.paas.pojo.dto.ImportFormDataDTO;
import com.xbongbong.paas.pojo.dto.SaasFormSaveDTO;
import com.xbongbong.paas.pojo.dto.ValidateDataDTO;
import com.xbongbong.paas.pojo.imports.CellTitlePojo;
import com.xbongbong.paas.pojo.imports.CellValuePojo;
import com.xbongbong.paas.pojo.imports.RowGroupPojo;
import com.xbongbong.paas.pojo.vo.PaasFormDataEsListVO;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.util.BeanUtil;
import com.xbongbong.paas.toolbox.util.DateTimeUtil;
import com.xbongbong.paas.toolbox.util.DateUtil;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.paas.util.ExplainUtil;
import com.xbongbong.pro.applist.pojo.AppListPojo;
import com.xbongbong.pro.applist.pojo.vo.ListAppVO;
import com.xbongbong.pro.constant.ElasticConstant;
import com.xbongbong.pro.domain.entity.UpdateDataEntity;
import com.xbongbong.pro.enums.ErrorCodeEnum;
import com.xbongbong.pro.enums.errorcodes.CreditManagement;
import com.xbongbong.pro.enums.errorcodes.SystemErrorCodeEnum;
import com.xbongbong.saas.constant.BasicConstant;
import com.xbongbong.saas.constant.ParameterConstant;
import com.xbongbong.saas.constant.StringConstant;
import com.xbongbong.saas.domain.entity.ext.CreditLimitEntityExt;
import com.xbongbong.saas.domain.entity.ext.CustomerEntityExt;
import com.xbongbong.saas.enums.CreditCardBillTypeEnum;
import com.xbongbong.saas.enums.CreditLimitStatusEnum;
import com.xbongbong.saas.enums.CreditTemporaryApplyStatusEnum;
import com.xbongbong.saas.enums.DistributorCreditStatusEnum;
import com.xbongbong.saas.enums.XbbRefTypeEnum;
import com.xbongbong.saas.enums.business.CreditCardBillEnum;
import com.xbongbong.saas.enums.business.CreditFixedLimitEnum;
import com.xbongbong.saas.enums.business.CreditLimitEnum;
import com.xbongbong.saas.enums.business.CreditTemporaryLimitEnum;
import com.xbongbong.saas.enums.business.DistributorEnum;
import com.xbongbong.saas.help.ImportHelper;
import com.xbongbong.saas.help.SaasSaveHelp;
import com.xbongbong.saas.model.CreditLimitModel;
import com.xbongbong.saas.model.CustomerModel;
import com.xbongbong.saas.service.CreditLimitService;
import com.xbongbong.saas.toolbox.help.FastJsonHelper;
import org.apache.commons.collections4.CollectionUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.SumAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.index.query.QueryBuilders.termsQuery;

/**
 * @author xingxing.xiao
 * @date 2020/12/25 11:12
 */
@Service("creditLimitService")
public class CreditLimitServiceImpl implements CreditLimitService {

    private static final Logger LOG = LoggerFactory.getLogger(CreditLimitServiceImpl.class);

    @Resource
    private SaasSaveHelp saasSaveHelp;
    @Resource
    private PaasFormModel paasFormModel;
    @Resource
    private PaasEsModel paasEsModel;
    @Resource
    private PaasFormExplainModel paasFormExplainModel;
    @Resource
    private XbbElasticsearchRestTemplate xbbElasticsearchRestTemplate;
    @Resource
    private ImportHelper importHelper;
    @Resource
    private CustomerModel customerModel;
    @Resource
    private CreditLimitModel creditLimitModel;
    @Resource
    private EsHelper esHelper;

    /**
     * 保存前saas特殊校验
     * @param validateDataDTO saas特殊校验数据入参
     * @throws XbbException
     */
    @Override
    public void beforeSave(ValidateDataDTO validateDataDTO) throws XbbException {
        Long temporaryId = validateDataDTO.getDataId();
        JSONObject data = validateDataDTO.getData();
        String corpid = validateDataDTO.getCorpid();
        Long formId = validateDataDTO.getFormId();
        // 处理关联客户
        JSONObject customerObj = data.getJSONArray(CreditLimitEnum.CUSTOMER_ID.getAttr()).getJSONObject(0);
        String customerName = customerObj.getString(StringConstant.SAAS_LINK_BUSINESS_NAME);
        saasSaveHelp.formatLinkBusiness4Save(data, CreditLimitEnum.CUSTOMER_ID.getAttr(), CreditLimitEnum.CUSTOMER_NAME.getAttr(), CreditLimitEnum.CUSTOMER_ID.getFieldType());
        boolean isNew = (temporaryId == null || Objects.equals(temporaryId, 0L));
        if (isNew) {
            // 保证客户只有一个
            String keyword = StringConstant.POINT + StringConstant.KEY_WORD;
            Long customerId = data.getLong(CreditFixedLimitEnum.CUSTOMER_ID.getAttr());
            BoolQueryBuilder boolQueryBuilder = boolQuery();
            boolQueryBuilder.filter(termQuery(ParameterConstant.CORPID + keyword, corpid));
            boolQueryBuilder.filter(termQuery(ParameterConstant.DEL, DelEnum.NORMAL.getDel()));
            boolQueryBuilder.filter(termQuery(ParameterConstant.FORMID, formId));
            boolQueryBuilder.filter(termQuery(CreditLimitEnum.getEsAttr4Keyword(CreditLimitEnum.CUSTOMER_ID), customerId));
            long count = paasEsModel.count(boolQueryBuilder, IndexTypeEnum.IDX_SAAS_CREDIT_LIMIT);
            if (count > 0) {
                throw new XbbException(CreditManagement.API_ERROR_253001);
            }
            // 排除不允许信用额度支付的经销商
            CustomerEntityExt customerEntityExt = customerModel.getByKey(customerId, corpid);
            if (Objects.nonNull(customerEntityExt)) {
                JSONObject customerData = customerEntityExt.getData();
                String paymentCreditStatus = customerData.getString(DistributorEnum.PAYMENT_BY_CREDIT.getAttr());
                if (Objects.equals(paymentCreditStatus, DistributorCreditStatusEnum.NOT_ALLOW.getCode())) {
                    throw new XbbException(CreditManagement.API_ERROR_253009, String.format(I18nMessageUtil.getMessage(CreditManagement.API_ERROR_253009.getMsg()), customerName));
                }
            }

            // 额度不能为负数
            Double fixedLimit = data.getDouble(CreditLimitEnum.FIXED_LIMIT.getAttr());
            if (fixedLimit < 0) {
                throw new XbbException(CreditManagement.API_ERROR_253008, String.format(I18nMessageUtil.getMessage(CreditManagement.API_ERROR_253008.getMsg()), CreditLimitEnum.FIXED_LIMIT.getAttrName()));
            }

            // 字段给定默认值
            data.put(CreditLimitEnum.STATUS.getAttr(), CreditLimitStatusEnum.NORMAL.getCode());
            data.put(CreditLimitEnum.TEMPORARY_LIMIT.getAttr(), 0);
            data.put(CreditLimitEnum.USED_LIMIT.getAttr(), 0);
            data.put(CreditLimitEnum.AVAILABLE_LIMIT.getAttr(), 0);
        }
    }

    /**
     * 保存后逻辑
     * @param saasFormSaveDTO saas保存数据
     * @throws XbbException
     */
    @Override
    public void afterSave(SaasFormSaveDTO saasFormSaveDTO) throws XbbException {

    }

    /**
     * 设置保存固定额度所需参数
     * @author xingxing.xiao
     */
    @Override
    public FormDataAddDTO setFixedFormDataAddDTO(SaasFormSaveDTO saasFormSaveDTO, Double fixedLimit) throws XbbException {
        FormDataAddDTO formDataSaveDTO = new FormDataAddDTO();
        BeanUtil.copyProperties(saasFormSaveDTO, formDataSaveDTO);

        String corpid = saasFormSaveDTO.getCorpid();
        Integer distributorMark = saasFormSaveDTO.getDistributorMark();
        int businessType = XbbRefTypeEnum.CREDIT_FIXED_LIMIT.getCode();
        String columns = "id, corpid, app_id, menu_id, name, saas_mark, business_type, is_process_form, distributor_mark";
        PaasFormEntityExt formEntityExt = paasFormModel.getByBusinessType4Distributor(businessType, corpid, distributorMark, columns);
        if (Objects.isNull(formEntityExt)) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001, MessageConstant.FORM_NOT_EXIST);
        }
        Long formId = formEntityExt.getId();
        formDataSaveDTO.setAppId(formEntityExt.getAppId());
        formDataSaveDTO.setMenuId(formEntityExt.getMenuId());
        formDataSaveDTO.setFormId(formId);
        formDataSaveDTO.setBusinessType(businessType);
        formDataSaveDTO.setSubBusinessType(businessType);
        formDataSaveDTO.setSaasMark(formEntityExt.getSaasMark());

        // 编号处理
        PaasFormExplainEntity paasFormExplainEntity = paasFormExplainModel.getByFormId(formId, corpid);
        if ( Objects.isNull(paasFormExplainEntity) ) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001, MessageConstant.FORM_EXPLAIN_NOT_EXIST);
        }
        List<FieldAttrEntity> explainList = JSONArray.parseArray(paasFormExplainEntity.getExplains(), FieldAttrEntity.class);
        Map<String, FieldAttrEntity> explainMap = explainList.stream().collect(Collectors.toMap(FieldAttrEntity::getAttr, item -> item));
        String serialNo = saasSaveHelp.getSerialNo("", corpid, formId, explainMap.get(CreditFixedLimitEnum.CREDIT_FIXED_NO.getAttr()));
        formDataSaveDTO.setSerialNo(serialNo);
        // data 处理
        JSONObject datalist = new JSONObject();
        JSONObject newData = saasFormSaveDTO.getNewData();
        datalist.put(CreditFixedLimitEnum.MODIFY_REASON.getAttr(), "初始化开通额度");
        datalist.put(CreditFixedLimitEnum.APPLY_LIMIT.getAttr(), fixedLimit);
        datalist.put(CreditFixedLimitEnum.OLD_LIMIT.getAttr(), 0L);
        // 关联客户处理
        Long customerId = newData.getLong(CreditLimitEnum.CUSTOMER_ID.getAttr());
        String customerName = newData.getString(CreditLimitEnum.CUSTOMER_NAME.getAttr());
        JSONArray customerArray = new JSONArray();
        JSONObject customerObj = new JSONObject();
        customerObj.put(StringConstant.SAAS_LINK_BUSINESS_ID, customerId);
        customerObj.put(StringConstant.SAAS_LINK_BUSINESS_NAME, customerName);
        customerObj.put(StringConstant.SAAS_LINK_BUSINESS_LINK_TYPE, XbbRefTypeEnum.CUSTOMER.getCode());
        customerArray.add(customerObj);
        datalist.put(CreditFixedLimitEnum.CUSTOMER_ID.getAttr(), customerArray);
        datalist.put(CreditFixedLimitEnum.CUSTOMER_NAME.getAttr(), customerName);
        datalist.put(CreditFixedLimitEnum.CREDIT_ID.getAttr(), saasFormSaveDTO.getNewPaasFormDataEntity().getId());
        datalist.put(CreditFixedLimitEnum.APPLY_ID.getAttr(), saasFormSaveDTO.getLoginUser().getUserId());
        datalist.put(CreditFixedLimitEnum.APPLY_NAME.getAttr(), saasFormSaveDTO.getLoginUser().getName());
        datalist.put(CreditFixedLimitEnum.APPLY_TIME.getAttr(), DateUtil.getInt());
        formDataSaveDTO.setDataList(datalist);
        return formDataSaveDTO;
    }

    /**
     * 计算有效期内临时额度之和
     * @param corpid
     * @param distributorMark
     * @param paasFormDataEsListVO
     * @throws XbbException
     */
    @Override
    public void calculateCreditTemporaryLimit(String corpid, Integer distributorMark, PaasFormDataEsListVO paasFormDataEsListVO) throws XbbException {
        List<PaasFormDataEntityExt> esList = paasFormDataEsListVO.getPaasFormDataESList();
        if (esList.size() > 0) {
            Set<String> customerIds = new HashSet<>();
            for (PaasFormDataEntityExt entityExt : esList) {
                JSONArray customerArray = entityExt.getData().getJSONArray(CreditLimitEnum.CUSTOMER_ID.getAttr());
                JSONObject customerObj = (JSONObject) customerArray.get(0);
                String customerId = customerObj.getString(StringConstant.SAAS_LINK_BUSINESS_ID);
                customerIds.add(customerId);
            }
            PaasFormEntityExt paasFormEntityExt = paasFormModel.getByBusinessType4Distributor(XbbRefTypeEnum.CREDIT_TEMPORARY_LIMIT.getCode(), corpid, distributorMark, "id, name, business_type");
            if (Objects.isNull(paasFormEntityExt)) {
                throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001, MessageConstant.FORM_NOT_EXIST);
            }
            BoolQueryBuilder boolQueryBuilder = boolQuery();
            boolQueryBuilder.filter(termsQuery(CreditTemporaryLimitEnum.getEsAttr4Keyword(CreditTemporaryLimitEnum.CUSTOMER_ID), customerIds));
            boolQueryBuilder.filter(termQuery(CreditTemporaryLimitEnum.getEsAttr4Keyword(CreditTemporaryLimitEnum.APPLY_STATUS), CreditTemporaryApplyStatusEnum.NORMAL.getCode()));
            boolQueryBuilder.filter(rangeQuery(CreditTemporaryLimitEnum.getEsAttr4Keyword(CreditTemporaryLimitEnum.END_TIME)).gte(DateTimeUtil.getTodayInt()));
            String temporaryField = CreditTemporaryLimitEnum.getEsAttr4Keyword(CreditTemporaryLimitEnum.CUSTOMER_ID);
            String temporarySubField = CreditTemporaryLimitEnum.getEsAttr4Keyword(CreditTemporaryLimitEnum.APPLY_TEMPORARY_LIMIT);
            Map<String, BigDecimal> temporaryMap = getCreditAggResult(corpid, paasFormEntityExt.getId(), IndexTypeEnum.IDX_SAAS_CREDIT_TEMPORARY_LIMIT, boolQueryBuilder, temporaryField, temporarySubField, customerIds.size());
            // 更新额度相关参数
            for (PaasFormDataEntityExt entityExt : esList) {
                JSONObject data = entityExt.getData();
                JSONArray customerArray = data.getJSONArray(CreditLimitEnum.CUSTOMER_ID.getAttr());
                JSONObject customerObj = (JSONObject) customerArray.get(0);
                String customerId = customerObj.getString(StringConstant.SAAS_LINK_BUSINESS_ID);
                if (temporaryMap.containsKey(customerId)) {
                    data.put(CreditLimitEnum.TEMPORARY_LIMIT.getAttr(), temporaryMap.get(customerId).setScale(2, BigDecimal.ROUND_HALF_UP));
                }
            }
        }
    }

    /**
     * 计算信用额度相关内容 - 临时额度、已用额度、可用额度
     * @param
     * @author xingxing.xiao
     */
    @Override
    public void calculateCreditLimit(String corpid, Integer distributorMark, List<PaasFormDataEntityExt> esList) throws XbbException {
        if (esList.size() > 0) {
            Set<String> customerIds = new HashSet<>();
            Map<String, BigDecimal> fixedLimitMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
            for (PaasFormDataEntityExt entityExt : esList) {
                Object customerObj = entityExt.getData().get(CreditLimitEnum.CUSTOMER_ID.getAttr());
                String customerId = "";
                if (customerObj instanceof JSONArray) {
                    JSONArray customerArray = entityExt.getData().getJSONArray(CreditLimitEnum.CUSTOMER_ID.getAttr());
                    JSONObject customerJson = (JSONObject) customerArray.get(0);
                    customerId = customerJson.getString(StringConstant.SAAS_LINK_BUSINESS_ID);
                } else {
                    customerId = customerObj.toString();
                }
                customerIds.add(customerId);
                BigDecimal fixedLimit = entityExt.getData().getBigDecimal(CreditLimitEnum.FIXED_LIMIT.getAttr());
                fixedLimitMap.put(customerId, fixedLimit);
            }

            // 临时额度、信用流水的 formId
            Map<Integer, Long> formMap = getCreditFormIds(corpid, distributorMark);
            int size = customerIds.size();

            // 1. 计算有效期内临时额度之和
            Long temporaryFormId = formMap.get(XbbRefTypeEnum.CREDIT_TEMPORARY_LIMIT.getCode());
            BoolQueryBuilder boolQueryBuilder = boolQuery();
            boolQueryBuilder.filter(termsQuery(CreditTemporaryLimitEnum.getEsAttr4Keyword(CreditTemporaryLimitEnum.CUSTOMER_ID), customerIds));
            boolQueryBuilder.filter(termQuery(CreditTemporaryLimitEnum.getEsAttr4Keyword(CreditTemporaryLimitEnum.APPLY_STATUS), CreditTemporaryApplyStatusEnum.NORMAL.getCode()));
            boolQueryBuilder.filter(rangeQuery(CreditTemporaryLimitEnum.getEsAttr4Keyword(CreditTemporaryLimitEnum.END_TIME)).gte(DateTimeUtil.getTodayInt()));
            String temporaryField = CreditTemporaryLimitEnum.getEsAttr4Keyword(CreditTemporaryLimitEnum.CUSTOMER_ID);
            String temporarySubField = CreditTemporaryLimitEnum.getEsAttr4Keyword(CreditTemporaryLimitEnum.APPLY_TEMPORARY_LIMIT);
            Map<String, BigDecimal> temporaryMap = getCreditAggResult(corpid, temporaryFormId, IndexTypeEnum.IDX_SAAS_CREDIT_TEMPORARY_LIMIT, boolQueryBuilder, temporaryField, temporarySubField, size);

            // 2. 计算已用额度 = 订货单 - (付款单 + 订货单关闭 + 订货单作废)
            Long cardBillFormId = formMap.get(XbbRefTypeEnum.CREDIT_CARD_BILL.getCode());
            String cardBillField = CreditCardBillEnum.getEsAttr4Keyword(CreditCardBillEnum.CUSTOMER_ID);
            String cardBillSubField = CreditCardBillEnum.getEsAttr4Keyword(CreditCardBillEnum.AMOUNT);
            // 2.1 订货单
            boolQueryBuilder = boolQuery();
            boolQueryBuilder.filter(termsQuery(CreditCardBillEnum.getEsAttr4Keyword(CreditCardBillEnum.CUSTOMER_ID), customerIds));
            boolQueryBuilder.filter(termQuery(CreditCardBillEnum.getEsAttr4Keyword(CreditCardBillEnum.TYPE), CreditCardBillTypeEnum.ORDER.getCode()));
            Map<String, BigDecimal> orderMap = getCreditAggResult(corpid, cardBillFormId, IndexTypeEnum.IDX_SAAS_CREDIT_CARD_BILL, boolQueryBuilder, cardBillField, cardBillSubField, size);
            // 2.2 付款单、订货单关闭、订货单作废
            boolQueryBuilder = boolQuery();
            boolQueryBuilder.filter(termsQuery(CreditCardBillEnum.getEsAttr4Keyword(CreditCardBillEnum.CUSTOMER_ID), customerIds));
            boolQueryBuilder.filter(termsQuery(CreditCardBillEnum.getEsAttr4Keyword(CreditCardBillEnum.TYPE), Arrays.asList(CreditCardBillTypeEnum.PAYMENT_SHEET.getCode(), CreditCardBillTypeEnum.ORDER_CLOSE.getCode(), CreditCardBillTypeEnum.ORDER_INVALID.getCode())));
            Map<String, BigDecimal> paymentMap = getCreditAggResult(corpid, cardBillFormId, IndexTypeEnum.IDX_SAAS_CREDIT_CARD_BILL, boolQueryBuilder, cardBillField, cardBillSubField, size);

            Map<String, BigDecimal> usedLimitMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
            if (!orderMap.isEmpty()) {
                for (String customerId : customerIds) {
                    BigDecimal orderAmount = orderMap.get(customerId);
                    BigDecimal paymentAmount = paymentMap.get(customerId);
                    if (Objects.nonNull(orderAmount)) {
                        if (Objects.nonNull(paymentAmount)) {
                            orderAmount = orderAmount.subtract(paymentAmount);
                        }
                        usedLimitMap.put(customerId, orderAmount);
                    }
                }
            }

            // 计算可用额度 = 固定额度 + 临时额度之和 - 已用额度
            Map<String, BigDecimal> availableMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
            for (String customerId : customerIds) {
                BigDecimal fixedLimit = fixedLimitMap.get(customerId);
                BigDecimal temporaryLimit = temporaryMap.get(customerId);
                BigDecimal usedLimit = usedLimitMap.get(customerId);
                if (Objects.nonNull(temporaryLimit)) {
                    fixedLimit = fixedLimit.add(temporaryLimit);
                }
                if (Objects.nonNull(usedLimit)) {
                    fixedLimit = fixedLimit.subtract(usedLimit);
                }
                availableMap.put(customerId, fixedLimit);
            }

            // 更新额度相关参数
            for (PaasFormDataEntityExt entityExt : esList) {
                JSONObject data = entityExt.getData();
                Object customerObj = data.get(CreditLimitEnum.CUSTOMER_ID.getAttr());
                String customerId = "";
                if (customerObj instanceof JSONArray) {
                    JSONArray customerArray = entityExt.getData().getJSONArray(CreditLimitEnum.CUSTOMER_ID.getAttr());
                    JSONObject customerJson = (JSONObject) customerArray.get(0);
                    customerId = customerJson.getString(StringConstant.SAAS_LINK_BUSINESS_ID);
                } else {
                    customerId = customerObj.toString();
                }
                if (temporaryMap.containsKey(customerId)) {
                    data.put(CreditLimitEnum.TEMPORARY_LIMIT.getAttr(), temporaryMap.get(customerId).setScale(2, BigDecimal.ROUND_HALF_UP));
                }
                if (usedLimitMap.containsKey(customerId)) {
                    data.put(CreditLimitEnum.USED_LIMIT.getAttr(), usedLimitMap.get(customerId).setScale(2, BigDecimal.ROUND_HALF_UP));
                }
                data.put(CreditLimitEnum.AVAILABLE_LIMIT.getAttr(), availableMap.get(customerId).setScale(2, BigDecimal.ROUND_HALF_UP));
            }
        }
    }

    /**
     * 分组聚合查询
     *      eg. "aggs":{"agg1":{"terms":{"field":"data.text_2.keyword"},"aggs":{"agg2":{"sum":{"field":"data.num_2"}}}}}
     * @param indexTypeEnum
     * @param boolQueryBuilder
     * @author xingxing.xiao
     */
    private Map<String, BigDecimal> getCreditAggResult(String corpid, Long formId, IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, String field, String subfield, int size) throws XbbException {
        Map<String, BigDecimal> resultMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);

        // 共用查询参数
        String keyword = StringConstant.POINT + StringConstant.KEY_WORD;
        boolQueryBuilder.filter(termQuery(ParameterConstant.CORPID + keyword, corpid));
        boolQueryBuilder.filter(termQuery(ParameterConstant.FORMID, formId));
        boolQueryBuilder.filter(termQuery(ParameterConstant.DEL, DelEnum.NORMAL.getDel()));
        boolQueryBuilder.filter(termsQuery("flowStatus", Arrays.asList(FlowStatusEnum.NOT_NEED_APPROVAL.getType(), FlowStatusEnum.PASS.getType())));
        boolQueryBuilder.filter(termQuery(ElasticConstant.MY_JOIN_FIELD, indexTypeEnum.getType()));

        TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("agg001").field(field);
        termsAggregationBuilder.size(size);
        termsAggregationBuilder.executionHint(ElasticConstant.EXECUTION_HINT);
        SumAggregationBuilder sumAggregationBuilder = AggregationBuilders.sum("agg002").field(subfield);
        termsAggregationBuilder.subAggregation(sumAggregationBuilder);
        SearchRequest searchRequest = new SearchRequest(indexTypeEnum.getIndex());
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(boolQueryBuilder);
        sourceBuilder.aggregation(termsAggregationBuilder);
        sourceBuilder.size(0);
        searchRequest.source(sourceBuilder);
        JSONObject temporaryResultObj = xbbElasticsearchRestTemplate.getAggResponseJson(searchRequest);
        JSONArray buckets = temporaryResultObj.getJSONObject("aggregations").getJSONObject("agg001").getJSONArray("buckets");
        for (Object bucket : buckets) {
            JSONObject temporay = (JSONObject) bucket;
            String key = temporay.getString("key");
            BigDecimal bigDecimal = temporay.getJSONObject("agg002").getBigDecimal("value");
            resultMap.put(key, bigDecimal);
        }
        return resultMap;
    }

    /**
     * 临时额度、信用流水的 formId
     * @param corpid
     * @param distributorMark
     * @return
     * @throws XbbException
     */
    private Map<Integer, Long> getCreditFormIds(String corpid, Integer distributorMark) throws XbbException {
        Map<Integer, Long> formMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        Map<String, Object> params = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        params.put(ParameterConstant.COLUMNS, "id, name, business_type");
        params.put(ParameterConstant.CORPID, corpid);
        params.put("businessTypeList", Arrays.asList(XbbRefTypeEnum.CREDIT_TEMPORARY_LIMIT.getCode(), XbbRefTypeEnum.CREDIT_CARD_BILL.getCode()));
        params.put("distributorMark", distributorMark);
        params.put(ParameterConstant.DEL, DelEnum.NORMAL.getDel());
        List<PaasFormEntity> formEntities = paasFormModel.findEntity(params);
        if (CollectionUtils.isEmpty(formEntities)) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001, MessageConstant.FORM_NOT_EXIST);
        }
        formEntities.forEach(item -> formMap.put(item.getBusinessType(), item.getId()));
        return formMap;
    }

    /**
     * 计算单个经销商的临时额度、已用额度、可用额度
     * @param corpid
     * @param distributorMark
     * @param data
     * @return
     * @author xingxing.xiao
     */
    @Override
    public void calculateCreditLimitSingle(String corpid, Integer distributorMark, JSONObject data) throws XbbException {
        String customerId = data.getString(CreditLimitEnum.CUSTOMER_ID.getAttr());
        BigDecimal fixedLimit = data.getBigDecimal(CreditLimitEnum.FIXED_LIMIT.getAttr());

        // 临时额度、信用流水的 formId
        Map<Integer, Long> formMap = getCreditFormIds(corpid, distributorMark);

        // 1. 计算有效期内临时额度之和
        Long temporaryFormId = formMap.get(XbbRefTypeEnum.CREDIT_TEMPORARY_LIMIT.getCode());
        BoolQueryBuilder boolQueryBuilder = boolQuery();
        boolQueryBuilder.filter(termQuery(CreditTemporaryLimitEnum.getEsAttr4Keyword(CreditTemporaryLimitEnum.CUSTOMER_ID), customerId));
        boolQueryBuilder.filter(termQuery(CreditTemporaryLimitEnum.getEsAttr4Keyword(CreditTemporaryLimitEnum.APPLY_STATUS), CreditTemporaryApplyStatusEnum.NORMAL.getCode()));
        boolQueryBuilder.filter(rangeQuery(CreditTemporaryLimitEnum.getEsAttr4Keyword(CreditTemporaryLimitEnum.END_TIME)).gte(DateTimeUtil.getTodayInt()));
        BigDecimal temporaryLimit = getCreditAggSum(corpid, temporaryFormId, IndexTypeEnum.IDX_SAAS_CREDIT_TEMPORARY_LIMIT, boolQueryBuilder, CreditTemporaryLimitEnum.getEsAttr4Keyword(CreditTemporaryLimitEnum.APPLY_TEMPORARY_LIMIT));

        // 2. 计算已用额度 = 订货单 - (付款单 + 订货单关闭 + 订货单作废)
        Long cardBillFormId = formMap.get(XbbRefTypeEnum.CREDIT_CARD_BILL.getCode());
        // 2.1 订货单
        boolQueryBuilder = boolQuery();
        boolQueryBuilder.filter(termQuery(CreditCardBillEnum.getEsAttr4Keyword(CreditCardBillEnum.CUSTOMER_ID), customerId));
        boolQueryBuilder.filter(termQuery(CreditCardBillEnum.getEsAttr4Keyword(CreditCardBillEnum.TYPE), CreditCardBillTypeEnum.ORDER.getCode()));
        BigDecimal orderLimit = getCreditAggSum(corpid, cardBillFormId, IndexTypeEnum.IDX_SAAS_CREDIT_CARD_BILL, boolQueryBuilder, CreditCardBillEnum.getEsAttr4Keyword(CreditCardBillEnum.AMOUNT));
        // 2.2 付款单、订货单关闭、订货单作废
        boolQueryBuilder = boolQuery();
        boolQueryBuilder.filter(termQuery(CreditCardBillEnum.getEsAttr4Keyword(CreditCardBillEnum.CUSTOMER_ID), customerId));
        boolQueryBuilder.filter(termsQuery(CreditCardBillEnum.getEsAttr4Keyword(CreditCardBillEnum.TYPE), Arrays.asList(CreditCardBillTypeEnum.PAYMENT_SHEET.getCode(), CreditCardBillTypeEnum.ORDER_CLOSE.getCode(), CreditCardBillTypeEnum.ORDER_INVALID.getCode())));
        BigDecimal paymentLimit = getCreditAggSum(corpid, cardBillFormId, IndexTypeEnum.IDX_SAAS_CREDIT_CARD_BILL, boolQueryBuilder, CreditCardBillEnum.getEsAttr4Keyword(CreditCardBillEnum.AMOUNT));

        // 已用额度
        BigDecimal usedLimit = orderLimit.subtract(paymentLimit);

        // 可用额度 = 固定额度 + 临时额度之和 - 已用额度
        BigDecimal availableLimit = fixedLimit.add(temporaryLimit).subtract(usedLimit);

        data.put(CreditLimitEnum.TEMPORARY_LIMIT.getAttr(), temporaryLimit.setScale(2, BigDecimal.ROUND_HALF_UP));
        data.put(CreditLimitEnum.USED_LIMIT.getAttr(), usedLimit.setScale(2, BigDecimal.ROUND_HALF_UP));
        data.put(CreditLimitEnum.AVAILABLE_LIMIT.getAttr(), availableLimit.setScale(2, BigDecimal.ROUND_HALF_UP));
    }

    /**
     * 单个聚合查询
     *      eg. "aggs":{"agg1":{"terms":{"field":"data.text_2.keyword"},"aggs":{"agg2":{"sum":{"field":"data.num_2"}}}}}
     * @param indexTypeEnum
     * @param boolQueryBuilder
     * @author xingxing.xiao
     */
    private BigDecimal getCreditAggSum(String corpid, Long formId, IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, String field) throws XbbException {
        // 共用查询参数
        boolQueryBuilder.filter(termQuery(ParameterConstant.CORPID + StringConstant.POINT + StringConstant.KEY_WORD, corpid));
        boolQueryBuilder.filter(termQuery(ParameterConstant.FORMID, formId));
        boolQueryBuilder.filter(termQuery(ParameterConstant.DEL, DelEnum.NORMAL.getDel()));
        boolQueryBuilder.filter(termsQuery("flowStatus", Arrays.asList(FlowStatusEnum.NOT_NEED_APPROVAL.getType(), FlowStatusEnum.PASS.getType())));
        boolQueryBuilder.filter(termQuery(ElasticConstant.MY_JOIN_FIELD, indexTypeEnum.getType()));

        SumAggregationBuilder sumAggregationBuilder = AggregationBuilders.sum("agg001").field(field);
        SearchRequest searchRequest = new SearchRequest(indexTypeEnum.getIndex());
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(boolQueryBuilder);
        sourceBuilder.aggregation(sumAggregationBuilder);
        sourceBuilder.size(0);
        searchRequest.source(sourceBuilder);
        JSONObject responseJson = xbbElasticsearchRestTemplate.getAggResponseJson(searchRequest);
        BigDecimal result = responseJson.getJSONObject("aggregations").getJSONObject("agg001").getBigDecimal("value");
        return result;
    }

    /**
     * 根据 customerId 查询 creditLimitEntity
     * @param customerId
     * @param corpid
     * @return
     */
    @Override
    public CreditLimitEntityExt getCreditByCustomerId(Long customerId, String corpid) throws XbbException {
        List<Long> customerIdIn = Collections.singletonList(customerId);
        List<CreditLimitEntityExt> list = getCreditByCustomerIdIn(customerIdIn, corpid);
        CreditLimitEntityExt entityExt = new CreditLimitEntityExt();
        if (CollectionUtils.isNotEmpty(list)) {
            entityExt = list.get(0);
        }
        return entityExt;
    }

    @Override
    public List<CreditLimitEntityExt> getCreditByCustomerIdIn(Collection<Long> customerIdIn, String corpid) throws XbbException {
        if (CollectionUtils.isEmpty(customerIdIn)) {
            return new ArrayList<>();
        }
        BoolQueryBuilder boolQueryBuilder = boolQuery();
        String keyword = StringConstant.POINT + StringConstant.KEY_WORD;
        String dataPoint = StringConstant.JSON_DATA + StringConstant.POINT;
        boolQueryBuilder.filter(termQuery(ParameterConstant.CORPID + keyword, corpid));
        boolQueryBuilder.filter(termsQuery(dataPoint + CreditLimitEnum.CUSTOMER_ID.getAttr() + keyword, customerIdIn));
        boolQueryBuilder.filter(termQuery(ParameterConstant.DEL, DelEnum.NORMAL.getDel()));
        boolQueryBuilder.filter(termQuery(ElasticConstant.MY_JOIN_FIELD, IndexTypeEnum.IDX_SAAS_CREDIT_LIMIT.getType()));
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(boolQueryBuilder);
        SearchRequest searchRequest = new SearchRequest(IndexTypeEnum.IDX_SAAS_CREDIT_LIMIT.getIndex());
        searchRequest.source(sourceBuilder);
        List<PaasFormDataEntityExt> dataEntityList = paasEsModel.list(sourceBuilder, IndexTypeEnum.IDX_SAAS_CREDIT_LIMIT, Collections.singletonList(StringConstant.JSON_DATA), 1, customerIdIn.size());
        List<CreditLimitEntityExt> list = new ArrayList<>();
        for (PaasFormDataEntityExt dataEntityExt : dataEntityList) {
            CreditLimitEntityExt entityExt = new CreditLimitEntityExt();
            BeanUtil.copyProperties(dataEntityExt, entityExt);
            list.add(entityExt);
        }
        return list;
    }

    /**
     * 查询开通信用额度的经销商
     * @param corpid
     * @param distributorMark
     * @param creditStatus 信用账户状态 CreditLimitStatusEnum.java
     * @return
     */
    @Override
    public List<CreditLimitEntityExt> getCreditDistributors(String corpid, Integer distributorMark, String creditStatus) {
        List<CreditLimitEntityExt> dataEntityList = new ArrayList<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        try {
            String columns = "id, corpid, app_id, menu_id, name";
            PaasFormEntityExt formEntityExt = paasFormModel.getByBusinessType4Distributor(XbbRefTypeEnum.CREDIT_LIMIT.getCode(), corpid, distributorMark, columns);
            if (Objects.isNull(formEntityExt)) {
                throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001, MessageConstant.FORM_NOT_EXIST);
            }
            BoolQueryBuilder boolQueryBuilder = boolQuery();
            String keyword = StringConstant.POINT + StringConstant.KEY_WORD;
            String dataPoint = StringConstant.JSON_DATA + StringConstant.POINT;
            boolQueryBuilder.filter(termQuery(ParameterConstant.CORPID + keyword, corpid));
            boolQueryBuilder.filter(termQuery(ParameterConstant.FORMID, formEntityExt.getId()));
            boolQueryBuilder.filter(termQuery(dataPoint + CreditLimitEnum.STATUS.getAttr() + keyword, creditStatus));
            boolQueryBuilder.filter(termQuery(ParameterConstant.DEL, DelEnum.NORMAL.getDel()));
            boolQueryBuilder.filter(termQuery(ElasticConstant.MY_JOIN_FIELD, IndexTypeEnum.IDX_SAAS_CREDIT_LIMIT.getType()));
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(boolQueryBuilder);
            SearchRequest searchRequest = new SearchRequest(IndexTypeEnum.IDX_SAAS_CREDIT_LIMIT.getIndex());
            searchRequest.source(sourceBuilder);
            EsUtil.setPage(sourceBuilder, 1, SizeConstant.PAGE_SIZE);
            dataEntityList = xbbElasticsearchRestTemplate.findScrollList(CreditLimitEntityExt.class, searchRequest, SizeConstant.SCROLL_TIME_IN_MILLIS);
        } catch (XbbException e) {
            e.printStackTrace();
        }
        return dataEntityList;
    }

    /**
     * 格式化导出值
     *
     * @param paasFormDataESList 数据
     * @throws XbbException 异常
     */
    @Override
    public void formatExportValueBefore(List<PaasFormDataEntityExt> paasFormDataESList, FormDataListDTO formDataListDTO) throws XbbException {

        String corpid = formDataListDTO.getCorpid();
        Integer distributorMark = formDataListDTO.getDistributorMark();
        calculateCreditLimit(corpid, distributorMark, paasFormDataESList);
    }

    /**
     * 名称一致性同步
     * @param corpid
     * @param customerId
     * @param customerName
     * @return
     * @author xingxing.xiao
     * @date 2021/02/25 16:48
     */
    @Override
    public void dataConsistencyUpdateCustomer(String corpid, Long customerId, String customerName) throws XbbException {
        try {
            BoolQueryBuilder filter = boolQuery()
                    .filter(termQuery("corpid.keyword", corpid))
                    .filter(termQuery("del", 0))
                    .filter(termQuery(CreditLimitEnum.getEsAttr4Keyword(CreditLimitEnum.CUSTOMER_ID), customerId));
            List<String> fieldList = new ArrayList<>();
            fieldList.add(FieldTypeEnum.DATAID.getAlias());
            List<PaasFormDataEntityExt> esEntities = esHelper.findByScroll(IndexTypeEnum.IDX_SAAS_CREDIT_LIMIT, filter, PaasFormDataEntityExt.class, fieldList);
            Set<Long> ids = new HashSet<>();
            for (PaasFormDataEntityExt esEntity : esEntities) {
                ids.add(esEntity.getDataId());
            }
            Map<String, Object> param = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
            param.put("corpid", corpid);
            param.put("idIn", ids);
            param.put("del", 0);
            List<CreditLimitEntityExt> entityList = creditLimitModel.findEntities(param);
            if (CollectionUtils.isNotEmpty(entityList)) {
                List<UpdateDataEntity> updateList = new ArrayList<>();
                for (CreditLimitEntityExt entity : entityList) {
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put(CreditLimitEnum.CUSTOMER_NAME.getAttr(), customerName);
                    UpdateDataEntity updateData = ExplainUtil.getUpdateData(entity.getId(), jsonObject, corpid);
                    updateList.add(updateData);
                }
                creditLimitModel.updateBatch(updateList, corpid);
            }
        } catch (Exception e) {
            LOG.error("CreditLimitServiceImpl.dataConsistencyUpdateCustomer 出错， corpid=" + corpid + " customerId=" + customerId + " customerName=" + customerName, e);
        }
    }

    /**
     * 将Excel数据格式化成表单保存需要的JSON格式的数据
     *
     * @param importFormDataDTO 导入入参
     * @param titlePojoList     标题
     * @param rowGroupPojo      一个表单对应的一组Excel填写的数据
     * @param fieldAttrList     解释
     * @return FormDataAddDTO
     */
    @Override
    public FormDataAddDTO formatExcelValue(ImportFormDataDTO importFormDataDTO, List<CellTitlePojo> titlePojoList, RowGroupPojo rowGroupPojo, List<FieldAttrEntityForImport> fieldAttrList) throws XbbException {
        FormDataAddDTO formDataAddDTO = new FormDataAddDTO();
        BeanUtil.copyProperties(importFormDataDTO, formDataAddDTO);
        formDataAddDTO.setSerialNo("");
        JSONObject dataJson = new JSONObject();
        List<List<CellValuePojo>> rowValueList = rowGroupPojo.getRowValueList();
        // 分组的第一条数据作为给实体对象赋值的数据
        List<CellValuePojo> cellValueList = rowValueList.get(0);
        for (FieldAttrEntityForImport fieldAttr : fieldAttrList) {
            String attr = fieldAttr.getAttr();
            Integer fieldType = fieldAttr.getFieldType();
            String attrName = fieldAttr.getAttrName();
            Integer columnIndex = importHelper.getColumnIndexByTitle(titlePojoList, attrName, null);
            Object cellValue = importHelper.getValueByColumnIndex(cellValueList, columnIndex);
            if (Objects.equals(fieldType, FieldTypeEnum.SUB_FORM.getType())) {
                importHelper.formatSubFormValue4Import(rowValueList, titlePojoList, importFormDataDTO, fieldAttr, dataJson);
            } else {
                if (Objects.isNull(cellValue) || StringUtil.isEmpty(cellValue.toString())) {
                    continue;
                }
                if (ImportHelper.commonHandleFieldType.contains(fieldAttr.getFieldType())) {
                    importHelper.formatCommonField(dataJson, fieldAttr, cellValue);
                } else if (Objects.equals(fieldType, FieldTypeEnum.SERIALNO.getType())) {
                    formDataAddDTO.setSerialNo(cellValue.toString());
                } else if (Objects.equals(fieldType, FieldTypeEnum.LINK_DATA.getType())) {
                    importHelper.formatLinkData(cellValue, fieldAttr, dataJson);
                } else if (Objects.equals(fieldType, FieldTypeEnum.LINK_DATA_MULTI.getType())) {
                    importHelper.formatLinkDataMulti(cellValue, importFormDataDTO, fieldAttr, dataJson);
                } else {
                    dataJson.put(attr, cellValue);
                }
            }
        }
        formDataAddDTO.setDataList(dataJson);
        return formDataAddDTO;
    }

    /**
     * 格式化移动端列表页
     * @param formDataListDTO 用户
     * @param esEntities 业务实体集合
     * @param totalCount 总数
     * @return
     * @throws XbbException
     */
    @Override
    public ListAppVO formatCreditAppList(FormDataListDTO formDataListDTO, List<PaasFormDataEntityExt> esEntities, Integer totalCount) throws XbbException {
        List<AppListPojo> appLists = new ArrayList<>();
        for (PaasFormDataEntityExt entity : esEntities) {
            JSONObject data = entity.getData();
            JSONObject superScript = new JSONObject();
            JSONObject others = new JSONObject();
            List<SummaryDataPoJo> summaryList = FastJsonHelper.getJsonArrOrDefaultFromFormData(data, BasicConstant.SUMMARY_LIST, new JSONArray()).toJavaList(SummaryDataPoJo.class);
            List<SummaryDataPoJo> lablesList = FastJsonHelper.getJsonArrOrDefaultFromFormData(data, BasicConstant.LABELS_LIST, new JSONArray()).toJavaList(SummaryDataPoJo.class);
            List<FormTitlePoJo> titleList = FastJsonHelper.getJsonArrOrDefaultFromFormData(data, BasicConstant.TITLE_LIST, new JSONArray()).toJavaList(FormTitlePoJo.class);
            appLists.add(new AppListPojo(entity.getId(), titleList, summaryList, superScript, lablesList, others, entity.getAppId(), entity.getMenuId(), entity.getFormId()));
        }

        ListAppVO listAppVO = new ListAppVO();
        listAppVO.setList(appLists);
        listAppVO.setTotalCount(totalCount);
        return  listAppVO;
    }
}
